<script>
import { onMounted, onBeforeUnmount, ref, reactive, toRefs } from "vue";
import * as THREE from "three";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import * as dat from "dat.gui";
import h337 from "heatmap.js";
import {
  CSS2DRenderer,
  CSS2DObject,
} from "three/examples/jsm/renderers/CSS2DRenderer";
export default {
  props: {
    mapLabel: { type: Object, default: {} }, //字段-标签映射关系列表  {name:'姓名', class:'班级'， ...}
    formDataPost: { type: Array, default: [] }, //增加对话框标签列表
    formDataPut: { type: Array, default: [] }, //修改查对话框标签列表
    flowData: { type: Array, default: [] }, //客流数据列表
    cameraData: { type: Array, default: [] }, //摄像头数据列表
    tracksData: { type: Array, default: [] }, //行人轨迹数据列表
  },
  emits: [
    "postData", //增加
    "putData", //修改
  ],
  setup(props, { emit }) {
    const state = reactive({
      labelData: { id: "", name: "" },
      dialogVisible: false,
      dialogTitle: "",
      dialogFormData: null,
      rowEdit: null,
    });

    const canvasWrapper = ref();
    const labelWrapper = ref();
    let scene, camera, renderer, labelRenderer;
    let FloorOne = new THREE.Group();
    let FloorTwo = new THREE.Group();
    let group = new THREE.Group();
    let modelIndex = 2; //需要加载的模型总数
    let controls;
    let moveMixer;
    const vector = new THREE.Vector3(); //三维坐标对象
    let clock = new THREE.Clock();
    const scale = 0.1;
    const [cameraX, cameraY, cameraZ] = [0, 3, 6];
    const bgColor = new THREE.Color("#f0f0f0");
    let label;
    let selectedMaterial;
    let gui = new dat.GUI();
    const [valueRangeLeft, valueRangeRight] = [0, 10000]; //客流人数比列尺
    const [rbgRangeLeft, rbgRangeRight] = ["#ffffff", "#800000"]; //颜色比例尺
    let mallState = {
      cameraView: false,
      floorSelect: "全部楼层",
      flowShow: false,
    };

    //初始化渲染器
    const initRenderer = () => {
      renderer = new THREE.WebGLRenderer({
        antialias: true,
      });
      renderer.setSize(
        canvasWrapper.value.clientWidth,
        canvasWrapper.value.clientHeight
      );
      canvasWrapper.value.appendChild(renderer.domElement);

      labelRenderer = new CSS2DRenderer();
      labelRenderer.setSize(
        canvasWrapper.value.clientWidth,
        canvasWrapper.value.clientHeight
      );
      labelRenderer.domElement.style.position = "relative";
      // 相对鼠标的相对偏移
      labelRenderer.domElement.style.top = `-${renderer.domElement.clientHeight}px`;
      canvasWrapper.value.appendChild(labelRenderer.domElement);
    };

    //初始化场景
    const initScence = () => {
      scene = new THREE.Scene();
      scene.background = new THREE.Color(bgColor);
      // scene.fog = new THREE.Fog(0xe0e0e0, 20, 100); //定义每个物体的雾的类型
    };

    //初始化灯光
    const initLight = () => {
      scene.add(new THREE.AmbientLight(0xffffff, 0.5));
      const arr = [
        [100, 100, 100],
        [-100, 100, 100],
        [100, -100, 100],
      ];
      arr.forEach((lightArr) => {
        let directionalLight = new THREE.DirectionalLight(0xffffff, 0.5);
        let [x, y, z] = lightArr;
        directionalLight.position.set(x, y, z);
        scene.add(directionalLight);
      });
    };

    //初始化物体
    const initMeshes = () => {
      scene.add(group);
      group.add(FloorOne);
      group.add(FloorTwo);
      //引入gltf模型
      const loader = new GLTFLoader();
      loader.load(
        "/3d-model/FLOOR_1.glb",
        (gltf) => {
          let model = gltf.scene;
          FloorOne.add(model);

          FloorOne.name = "FloorOne";
          model.scale.set(scale, scale, scale);
          loadedModel(() => {
            if (props.flowData.length) initHeatMap();
            if (props.cameraData.length) initCameraPosition();
            if (props.tracksData.length) initPeopleTracks();
          });
        },
        (xhr) => {
          console.log(
            "加载完成的百分比" + (xhr.loaded / xhr.total) * 100 + "%"
          );
        }
      );
      loader.load(
        "/3d-model/FLOOR_2.glb",
        (gltf) => {
          let model = gltf.scene;
          FloorTwo.add(model);

          FloorTwo.name = "FloorTwo";
          model.scale.set(scale, scale, scale);
          loadedModel(() => {
            if (props.flowData.length) initHeatMap();
            if (props.cameraData.length) initCameraPosition();
            if (props.tracksData.length) initPeopleTracks();
          });
        },
        (xhr) => {
          console.log(
            "加载完成的百分比" + (xhr.loaded / xhr.total) * 100 + "%"
          );
        }
      );
    };

    //初始化相机
    const initCamera = () => {
      camera = new THREE.PerspectiveCamera(
        45,
        canvasWrapper.value.clientWidth / canvasWrapper.value.clientHeight,
        0.1,
        100000
      );
      camera.position.set(cameraX, cameraY, cameraZ);
    };

    //初始化控制器
    const initControls = () => {
      controls = new OrbitControls(camera, labelRenderer.domElement);
      // controls.autoRotate = true;
      // controls.enableZoom = false;
      // controls.enabled = false;
    };

    //渲染场景
    const animate = () => {
      requestAnimationFrame(animate);
      controls.update();
      const delta = clock.getDelta();
      if (moveMixer) moveMixer.update(delta);
      renderer.render(scene, camera);
      labelRenderer.render(scene, camera);
    };

    //防抖
    const debounce = (func, wait) => {
      let timeout;
      return function () {
        const context = this;
        const args = arguments;
        if (timeout) clearTimeout(timeout);
        timeout = setTimeout(() => {
          func.apply(context, args);
        }, wait);
      };
    };

    const resizeHandler = () => {
      // 重置渲染器输出画布canvas尺寸
      renderer.setSize(
        canvasWrapper.value.clientWidth,
        canvasWrapper.value.clientHeight
      );
      labelRenderer.setSize(
        canvasWrapper.value.clientWidth,
        canvasWrapper.value.clientHeight
      );
      camera.aspect =
        canvasWrapper.value.clientWidth / canvasWrapper.value.clientHeight;
      camera.updateProjectionMatrix();
    };

    const clickHandler = (event) => {
      // 鼠标点击显示相关信息
      //获取三维坐标
      vector.set(
        (event.offsetX / canvasWrapper.value.clientWidth) * 2 - 1,
        -(event.offsetY / canvasWrapper.value.clientHeight) * 2 + 1,
        0.5
      );
      let raycaster = new THREE.Raycaster();
      raycaster.setFromCamera(vector, camera);
      //筛选可见的物体
      let groupList = group.children.filter((v) => v.visible);
      let intersects = raycaster.intersectObjects(groupList);

      if (intersects.length > 0) {
        //选中物体
        updateLabal(intersects[0].object);
      } else {
        if (selectedMaterial) selectedMaterial.emissive.setHex(0);
        label.visible = false;
      }
    };

    /**
     * intersect参数是鼠标点击的部件
     * DOM 元素被包装到 CSS2DObject 的实例中并添加到scene中
     */
    const updateLabal = (selected) => {
      if (selectedMaterial) selectedMaterial.emissive.setHex(0);
      selected.material = selected.material.clone();
      selected.material.emissive.setHex(0xff0000);
      selectedMaterial = selected.material;
      label.position.set(
        selected.position.x * scale,
        (selected.position.y + 3) * scale,
        selected.position.z * scale
      );
      //拿到模型id请求接口，渲染数据
      state.labelData = selected;
      label.visible = true;
    };

    /**
     * 组件挂载时，创建作为label的2D对象
     * DOM 元素被包装到 CSS2DObject 的实例中并添加到scene中
     */
    const createTurbineLabel = () => {
      // div元素包装成为css2模型对象CSS2DObject

      label = new CSS2DObject(labelWrapper.value);
      // 设置HTML元素标签在three.js世界坐标中位置
      label.visible = false;
      scene.add(label);
    };

    const addEvent = () => {
      const resizeDebounce = debounce(resizeHandler, 500);

      window.addEventListener("resize", resizeDebounce);

      canvasWrapper.value.addEventListener("click", clickHandler);
    };

    const loadGui = () => {
      gui.addFolder("控件");
      // gui.add(controls, "autoRotate").name("自动旋转");
      // gui.add(camera, "fov").name("视角缩放").min(30).max(70).onChange(() => {
      //   camera.updateProjectionMatrix();
      // });
      // gui
      //   .add(mallState, "cameraView")
      //   .name("2D/3D")
      //   .onChange((val) => {
      //     if (val) moveAnimation(camera, 0, 8, 0);
      //     else moveAnimation(camera, cameraX, cameraY, cameraZ);
      //   });

      gui
        .add(mallState, "floorSelect", ["全部楼层", "1F", "2F"])
        .name("楼层选择")
        .onChange((val) => {
          if (val === "1F") {
            group.children.forEach((v) => (v.visible = false));
            let FloorOne = group.getObjectByName("FloorOne");
            FloorOne.visible = true;
            moveAnimation(group, 0, 0, 0);
          } else if (val === "2F") {
            group.children.forEach((v) => (v.visible = false));
            let FloorTwo = group.getObjectByName("FloorTwo");
            FloorTwo.visible = true;
            moveAnimation(group, 0, -1, 0);
          } else if (val === "全部楼层") {
            group.children.forEach((v) => (v.visible = true));
            moveAnimation(group, 0, 0, 0);
          }
        });
      gui.open();
      canvasWrapper.value.appendChild(gui.domElement);
      gui.domElement.style.position = "relative";
      gui.domElement.style.top = `-${renderer.domElement.clientHeight * 2}px`;
    };

    //平滑移动动画
    const moveAnimation = (mesh, x, y, z) => {
      const positionKF = new THREE.VectorKeyframeTrack(
        ".position",
        [0, 1],
        [mesh.position.x, mesh.position.y, mesh.position.z, x, y, z]
      );
      const clip = new THREE.AnimationClip("MoveAction", 1, [positionKF]);
      moveMixer = new THREE.AnimationMixer(mesh);
      const action = moveMixer.clipAction(clip);
      action.setLoop(THREE.LoopOnce);
      action.clampWhenFinished = true; //取消动画重置
      action.play();
    };

    //商铺颜色初始化
    // const showColorInit = () => {
    //   props.flowData.forEach((ele) => {
    //     let mesh = group.getObjectByName(ele.name);
    //     mesh.material = mesh.material.clone();
    //     if (ele.type === 0) mesh.material.color.setHex(0xffffff);
    //     else if (ele.type === 1) mesh.material.color.setHex(0xffdd55);
    //   });
    // };

    //摄像头位置初始化
    const initCameraPosition = () => {
      //设置透明
      group.children.forEach((ele) => {
        ele.children[0].children.forEach((v) => {
          v.material.transparent = true;
          v.material.opacity = 0.5;
        });
      });

      const sphereGeometry = new THREE.SphereGeometry(1 * scale, 20, 20);
      const sphereMaterial = new THREE.MeshStandardMaterial();
      props.cameraData.forEach((ele) => {
        let sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
        if (ele.cmPosFloor == "1") {
          sphere.name = ele.cmName;
          sphere.position.set(ele.cmPosPlaneX * scale, 8 * scale, ele.cmPosPlaneY * scale);
          FloorOne.add(sphere);
        } else if (ele.cmPosFloor == "2") {
          sphere.name = ele.cmName;
          sphere.position.set(ele.cmPosPlaneX * scale, 18 * scale, ele.cmPosPlaneY * scale);
          FloorTwo.add(sphere);
        }
      });
    };

    //热力图
    const initHeatMap = () => {
      // 获取Canvas元素
      const canvas = document.getElementById('heatmap');

      const heatmapInstance = h337.create({
        container: canvas,
        radius: 5,
      });

      // now generate some random data
      let points = [{
        x: 1,
        y: 1,
        value: 0
      }];
      var max = 10;
      var width = 200;
      var height = 200;
      var len = 600;

      while (len--) {
        var val = Math.floor(Math.random() * 100);
        max = Math.max(max, val);
        var point = {
          x: Math.floor(Math.random() * width),
          y: Math.floor(Math.random() * height),
          value: val
        };
        points.push(point);
      }
      // heatmap data format
      var data = {
        max: max,
        data: points
      };
      heatmapInstance.setData(data);
      // 生成纹理
      // 获取Canvas数据URL
      const heatmapTexture = new THREE.TextureLoader().load(heatmapInstance.getDataURL());
      // 纹理加载完成后的处理逻辑
      // 创建材质并将纹理应用到材质上
      const material = new THREE.MeshBasicMaterial({ map: heatmapTexture, transparent: true, });
      // 创建几何体
      const geometry = new THREE.PlaneBufferGeometry(5.3, 4);
      // 创建网格对象并应用材质
      const plan = new THREE.Mesh(geometry, material);
      // 将网格对象添加到场景中进行显示
      FloorTwo.add(plan);
      plan.rotation.x -= Math.PI / 2
      plan.position.y += 1.66
      plan.position.x -= 0.4
    }

    //行人轨迹
    const initPeopleTracks = () => {
      camera.position.set(0, 10, 0)
      // moveAnimation(camera, 0, 8, 0);
      const material = new THREE.LineBasicMaterial({ color: 0xff3333 });

      console

      let [pointsOne, pointsTwo] = [[], []];

      props.tracksData.forEach((v) => {
        if (v.floorId == "0") {
          pointsOne.push(
            new THREE.Vector3(v.xrPosPlaneX * scale, 8 * scale, v.xrPosPlaneY * scale)
          );
        }
        else if (v.floorId == "1") {
          pointsTwo.push(
            new THREE.Vector3(v.xrPosPlaneX * scale, 18 * scale, v.xrPosPlaneY * scale)
          );
        }
      })
      const geometryOne = new THREE.BufferGeometry().setFromPoints(pointsOne);
      const lineOne = new THREE.Line(geometryOne, material);
      FloorOne.add(lineOne);
      const geometryTwo = new THREE.BufferGeometry().setFromPoints(pointsTwo);
      const lineTwo = new THREE.Line(geometryTwo, material);
      FloorTwo.add(lineTwo);
    };

    /**
     * 单个模型加载完调用该函数
     * func--所有模型加载完毕调用
     */
    const loadedModel = (func) => {
      const context = this;
      const args = arguments;
      modelIndex--;
      if (!modelIndex) {
        func.apply(context, args);
      }
    };

    const addShop = () => {
      state.dialogFormData = {};
      props.formDataPost.forEach((key) => {
        state.dialogFormData[key] = "";
      });
      for (let key in state.dialogFormData) {
        state.dialogFormData[key] = "";
      }
      state.dialogVisible = true;
      state.dialogTitle = "添加租户";
    };

    const editShop = () => {
      state.dialogFormData = {};
      props.formDataPut.forEach((key) => {
        state.dialogFormData[key] = "";
      });
      for (let key in state.dialogFormData) {
        state.dialogFormData[key] = state.labelData[key];
      }
      state.dialogVisible = true;
      state.dialogTitle = "修改租户";
      state.rowEdit = state.labelData;
    };

    //添加数据
    const handleAdd = () => {
      emit("postData", state.dialogFormData);
    };

    //修改数据
    const handleEdit = () => {
      emit("putData", state.rowEdit, state.dialogFormData);
    };

    //点击table，对应商铺高亮显示
    const selectTable = (name) => {
      //筛选可见的物体
      let groupList = group.children.filter((v) => v.visible);
      let mesh;
      groupList.forEach((v) => {
        if (v.getObjectByName(name)) mesh = v.getObjectByName(name);
      });
      if (!mesh) {
        ElMessage.warning("该模型已被隐藏或未加载！！！");
        return;
      }
      updateLabal(mesh);
    };

    onMounted(() => {
      initRenderer();
      initScence();
      initLight();
      initMeshes();
      initCamera();
      initControls();
      createTurbineLabel();
      animate();
      addEvent();
      loadGui();
    });

    onBeforeUnmount(() => {
      scene.clear();
      renderer.dispose();
      renderer.forceContextLoss();
      renderer.content = null;
      cancelAnimationFrame(animate);
    });

    return {
      ...toRefs(state),
      canvasWrapper,
      labelWrapper,
      addShop,
      editShop,
      handleAdd,
      handleEdit,
      selectTable,
    };
  },
};
</script>

<template>
  <div id="heatmap" style="height: 200px;width: 200px;display: none;">
  </div>
  <div ref="canvasWrapper" style="width: 100%; height: 100%"></div>

  <div ref="labelWrapper" style="display: none; opacity: 0.8">
    <el-card>
      <div>id：{{ labelData.id }}</div>
      <div>name：{{ labelData.name }}</div>
      <div>info：</div>
      <div>
        <el-button v-if="formDataPost.length" @click="addShop">添加租户</el-button>
        <el-button v-if="formDataPut.length" @click="editShop">修改租户</el-button>
      </div>
    </el-card>
  </div>

  <!-- 对话框 -->
  <el-dialog v-model="dialogVisible" :title="dialogTitle">
    <el-form :model="dialogFormData" label-width="120px">
      <el-form-item v-for="(value, key, index) in dialogFormData" :key="index" :label="mapLabel[key] || key">
        <el-input />
      </el-form-item>
      <el-form-item v-if="dialogTitle === '添加租户'">
        <el-button type="primary" @click="handleAdd">添加</el-button>
      </el-form-item>
      <el-form-item v-if="dialogTitle === '修改租户'">
        <el-button type="primary" @click="handleEdit">修改</el-button>
      </el-form-item>
    </el-form>
  </el-dialog>
</template>

<style scoped></style>